package com.unicloud.medical.oss.common.beanFactoryPostProcessor;

import com.unicloud.medical.oss.common.constant.OSSPropertiesConstant;
import com.unicloud.medical.oss.common.enums.ErrorEnum;
import com.unicloud.medical.oss.common.model.OSSServerProperties;
import com.unicloud.medical.oss.common.util.EncryptionUtils;
import com.unisound.medical.common.exception.BusinessException;
import com.unisound.medical.common.util.AssertUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.PropertySource;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
public class AbstractBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, Ordered {
    private final Pattern pattern;
    private final List<OSSServerProperties> ossServerProperties;

    public AbstractBeanFactoryPostProcessor(ApplicationContext applicationContext, String ossType) {
        this.pattern = Pattern.compile("^" + OSSPropertiesConstant.OSS_PREFIX + "." + ossType + "\\[" + "(\\d)" + "\\]");
        this.ossServerProperties = getOSSServerProperties(applicationContext, ossType);
    }

    protected String type() {
        return null;
    }

    protected void registerBeanDefinition(BeanDefinitionRegistry registry, String name, BeanDefinition beanDefinition) {
        registry.registerBeanDefinition(name, beanDefinition);
    }

    protected List<OSSServerProperties> getOSSServerProperties(ApplicationContext applicationContext, String ossType) {
        Map<String, Object> propertySourceMap = propertySourceMap(applicationContext);
        Map<String, Map<String, Object>> properties = findProperties(propertySourceMap, ossType);
        return propertiesToOSSServerProperties(properties, ossType);
    }

    private Map<String, Object> propertySourceMap(ApplicationContext applicationContext) {
        Map<String, Object> propertySourceMap = new HashMap<>();
        AbstractEnvironment environment = (AbstractEnvironment) applicationContext.getEnvironment();
        for (PropertySource<?> propertySource : environment.getPropertySources()) {
            if (propertySource instanceof OriginTrackedMapPropertySource) {
                OriginTrackedMapPropertySource source = (OriginTrackedMapPropertySource)propertySource;
                propertySourceMap.putAll(source.getSource());
            }
        }
        return propertySourceMap;
    }

    private Map<String, Map<String, Object>> findProperties(Map<String, Object> propertySourceMap, String ossType) {
        Map<String, Map<String, Object>> properties = new HashMap<>();
        propertySourceMap.forEach((propertyName, propertyValue) -> {
            Matcher matcher = pattern.matcher(propertyName);
            if (matcher.find()) {
                String index = matcher.group(1);
                fillMap(properties, index, propertyName, propertyValue);
            }
        });
        return properties;
    }

    private void fillMap(Map<String, Map<String, Object>> properties, String index, String key, Object value) {
        Map<String, Object> valueMap = properties.get(index);
        if (valueMap == null) {
            valueMap = new HashMap<>();
            properties.put(index, valueMap);
        }
        valueMap.put(key, value);
    }

    private List<OSSServerProperties> propertiesToOSSServerProperties(Map<String, Map<String, Object>> properties, String ossType) {
        List<OSSServerProperties> ossServerPropertiesList = new ArrayList<>();
        properties.forEach((index, propertyMap) -> {
            String keyPrefix = OSSPropertiesConstant.OSS_PREFIX + "." + ossType + "[" + index + "].";
            OSSServerProperties ossServerProperties = mapToOSSServerProperties(propertyMap, keyPrefix);
            if (ossServerProperties != null) {
                ossServerPropertiesList.add(ossServerProperties);
            }
        });
        return ossServerPropertiesList;
    }

    private OSSServerProperties mapToOSSServerProperties(Map<String, Object> map, String keyPrefix) {
        if (MapUtils.isEmpty(map)) return null;
        try {
            String name = (String) get(map, keyPrefix, "name");
            String region = (String) get(map, keyPrefix, "region");
            String endpoint = (String) get(map, keyPrefix, "endpoint");
            String proxyEndpoint = (String) get(map, keyPrefix, "proxy-endpoint");
            if (StringUtils.isEmpty(proxyEndpoint)) {
                proxyEndpoint = (String) get(map, keyPrefix, "proxyEndpoint");
            }
            String accessKey = (String) get(map, keyPrefix, "access-key");
            if (StringUtils.isEmpty(accessKey)) {
                accessKey = (String) get(map, keyPrefix, "accessKey");
            }
            String secretKey = (String) get(map, keyPrefix, "secret-key");
            if (StringUtils.isEmpty(secretKey)) {
                secretKey = (String) get(map, keyPrefix, "secretKey");
            }
            Boolean checkEnabled = (Boolean) get(map, keyPrefix, "check.enabled");
            String checkBucketName = (String) get(map, keyPrefix, "check.bucket-name");
            if (StringUtils.isEmpty(checkBucketName)) {
                checkBucketName = (String) get(map, keyPrefix, "check.bucketName");
            }
            Boolean enabled = (Boolean) get(map, keyPrefix, "enabled");
            if (!BooleanUtils.isFalse(enabled)) {
                AssertUtil.assertTrue(StringUtils.isNotEmpty(endpoint), ErrorEnum.ENDPOINT_NOT_NULL);
                AssertUtil.assertTrue(StringUtils.isNotEmpty(accessKey), ErrorEnum.ACCESS_KEY_NOT_NULL);
                AssertUtil.assertTrue(StringUtils.isNotEmpty(secretKey), ErrorEnum.SECRET_KEY_NOT_NULL);
                secretKey = decryptSecretKey(secretKey);
            }

            OSSServerProperties ossServerProperties = new OSSServerProperties();
            ossServerProperties.setName(name);
            ossServerProperties.setRegion(region);
            ossServerProperties.setEndpoint(endpoint);
            ossServerProperties.setProxyEndpoint(proxyEndpoint);
            ossServerProperties.setAccessKey(accessKey);
            ossServerProperties.setSecretKey(secretKey);
            ossServerProperties.setEnabled(BooleanUtils.toBoolean(enabled));
            ossServerProperties.setCheck(new OSSServerProperties.Check(BooleanUtils.toBoolean(checkEnabled), checkBucketName));
            return ossServerProperties;
        } catch (Exception e) {
            log.error("mapToOSSServerProperties error. cause={}", e.getMessage());
            return null;
        }
    }

    private Object get(Map<String, Object> map, String keyPrefix, String name) {
        OriginTrackedValue o = (OriginTrackedValue) map.get(keyPrefix + name);
        if (o == null) return null;
        return o.getValue();
    }

    private String decryptSecretKey(String secretKey) {
        if (secretKey.startsWith("ENC(") && secretKey.endsWith(")")) {
            try {
                return EncryptionUtils.decrypt(secretKey.substring(4, secretKey.length() - 1));
            } catch (Exception e) {
                throw new BusinessException(ErrorEnum.SECRET_KEY_INVALID);
            }
        }
        return secretKey;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        if (CollectionUtils.isEmpty(ossServerProperties)) return;

        for (OSSServerProperties ossServerProperties : ossServerProperties) {
            if (!ossServerProperties.isEnabled()) {
                log.warn("配置已关闭 ossServerProperties={}", ossServerProperties);
            } else {
                try {
                    registerClient(ossServerProperties, registry);
                } catch (Exception e) {
                    log.error("postProcessBeanFactory error. cause={}", e.getMessage());
                }
            }
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    protected void registerClient(OSSServerProperties ossServerProperties, BeanDefinitionRegistry registry) throws Exception {

    }

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;
    }

}
